package interfaceUsuario;


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;


public class TCPPPChat implements Runnable {

	public static GUI gui = new GUI();
	// Connect status constants
	public final static int NULL = 0;
	public final static int DESCONECTADO = 1;
	public final static int DESCONECTANDO = 2;
	public final static int INICIANDO_CONEXAO = 3;
	public final static int CONECTADO = 4;

	// Other constants
	public final static String statusMensagem[] = {
			" Error! Could not connect!", " Disconnected", " Disconnecting...",
			" Connecting...", " Connected" };

	public final static TCPPPChat tcpObj = new TCPPPChat();
	public final static String FIM_SESSAO = new Character((char) 0).toString(); // Indicates the end of a session

	// Connection state info

	public static String hostIP = "localhost";
	public static int port = 1234;
	public static int connectionStatus = DESCONECTADO;
	public static boolean isHost = true;
	public static String statusString = statusMensagem[connectionStatus];
	public static StringBuffer toAppend = new StringBuffer("");
	public static StringBuffer toSend = new StringBuffer("");

	// TCP Components
	public static ServerSocket hostServer = null;
	public static Socket socket = null;
	public static BufferedReader entrada = null;
	public static PrintWriter saida = null;

	/////////////////////////////////////////////////////////////////

	private JPanel initOptionsPane() { // aqui tinha um static

		JPanel pane = null;
		ActionAdapter buttonListener = null;

		// Create an options pane
		JPanel optionsPane = new JPanel(new GridLayout(4, 1));

		// IP address input
		pane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		pane.add(new JLabel("Host IP:"));
		gui.ipField = new JTextField(10);
		gui.ipField.setText(hostIP);
		gui.ipField.setEnabled(false);
		gui.ipField.addFocusListener(new FocusAdapter() {
			/*public void focusLost(FocusEvent e) {
				gui.ipField.selectAll();
			   // Should be editable only when disconnected
			   if (connectionStatus != DESCONECTADO) {
			      changeStatusNTS(NULL, true);
			   }
			   else {
			      hostIP = gui.ipField.getText();
			   }
			}*/
		});
		pane.add(gui.ipField);
		optionsPane.add(pane);

		// Port input
		pane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		pane.add(new JLabel("Port:"));
		gui.portField = new JTextField(10);
		gui.portField.setEditable(true);
		gui.portField.setText((new Integer(port)).toString());
		gui.portField.addFocusListener(new FocusAdapter() {
			public void focusLost(FocusEvent e) {
				// should be editable only when disconnected
				if (connectionStatus != DESCONECTADO) {
					changeStatusNTS(NULL, true);
				} else {
					int temp;
					try {
						temp = Integer.parseInt(gui.portField.getText());
						port = temp;
					} catch (NumberFormatException nfe) {
						gui.portField.setText((new Integer(port)).toString());
						gui.mainFrame.repaint();
					}
				}
			}
		});
		pane.add(gui.portField);
		optionsPane.add(pane);

		// Host/guest option
		buttonListener = new ActionAdapter() {
			public void actionPerformed(ActionEvent e) {
				if (connectionStatus != DESCONECTADO) {
					changeStatusNTS(NULL, true);
				} else {
					isHost = e.getActionCommand().equals("host");

					// Cannot supply host IP if host option is chosen
					if (isHost) {
						gui.ipField.setEnabled(false);
						gui.ipField.setText("localhost");
						hostIP = "localhost";
					} else {
						gui.ipField.setEnabled(true);
					}
				}
			}
		};
		ButtonGroup bg = new ButtonGroup();
		gui.hostOption = new JRadioButton("Servidor", true);
		gui.hostOption.setMnemonic(KeyEvent.VK_H);
		gui.hostOption.setActionCommand("host");
		gui.hostOption.addActionListener(buttonListener);
		gui.guestOption = new JRadioButton("Cliente", false);
		gui.guestOption.setMnemonic(KeyEvent.VK_G);
		gui.guestOption.setActionCommand("guest");
		gui.guestOption.addActionListener(buttonListener);
		bg.add(gui.hostOption);
		bg.add(gui.guestOption);
		pane = new JPanel(new GridLayout(1, 2));
		pane.add(gui.hostOption);
		pane.add(gui.guestOption);
		optionsPane.add(pane);

		// Connect/disconnect buttons
		JPanel buttonPane = new JPanel(new GridLayout(1, 2));
		buttonListener = new ActionAdapter() {
			public void actionPerformed(ActionEvent e) {
				// Request a connection initiation
				if (e.getActionCommand().equals("connect")) {
					changeStatusNTS(INICIANDO_CONEXAO, true);
				}
				// Disconnect
				else {
					changeStatusNTS(DESCONECTANDO, true);
				}
			}
		};
		gui.connectButton = new JButton("Conectar");
		gui.connectButton.setMnemonic(KeyEvent.VK_C);
		gui.connectButton.setActionCommand("connect");
		gui.connectButton.addActionListener(buttonListener);
		gui.connectButton.setEnabled(true);
		gui.disconnectButton = new JButton("Desconectar");
		gui.disconnectButton.setMnemonic(KeyEvent.VK_D);
		gui.disconnectButton.setActionCommand("disconnect");
		gui.disconnectButton.addActionListener(buttonListener);
		gui.disconnectButton.setEnabled(false);
		buttonPane.add(gui.connectButton);
		buttonPane.add(gui.disconnectButton);
		optionsPane.add(buttonPane);

		return optionsPane;
	}

	/////////////////////////////////////////////////////////////////

	// Initialize all the GUI components and display the frame
	private void initGUI() {//tinha um static
		// Set up the status bar
		gui.statusField = new JLabel();
		gui.statusField.setText(statusMensagem[DESCONECTADO]);
		gui.statusColor = new JTextField(1);
		gui.statusColor.setBackground(Color.red);
		gui.statusColor.setEditable(false);
		gui.statusBar = new JPanel(new BorderLayout());
		gui.statusBar.add(gui.statusColor, BorderLayout.WEST);
		gui.statusBar.add(gui.statusField, BorderLayout.CENTER);

		// Set up the options pane
		JPanel optionsPane = initOptionsPane();

		// Set up the chat pane
		JPanel chatPane = new JPanel(new BorderLayout());
		gui.chatText = new JTextArea(10, 20);
		gui.chatText.setLineWrap(true);
		gui.chatText.setEditable(false);
		gui.chatText.setForeground(Color.blue);
		JScrollPane chatTextPane = new JScrollPane(gui.chatText,
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		gui.chatLine = new JTextField();
		gui.chatLine.setEnabled(false);
		gui.chatLine.addActionListener(new ActionAdapter() {
			public void actionPerformed(ActionEvent e) {
				//            	TCPPPChat gui = new TCPPPChat();
				String s = gui.chatLine.getText();
				if (!s.equals("")) {
					appendToChatBox("Saida: " + s + "\n");
					gui.chatLine.selectAll();

					// Send the string
					sendString(s);
				}
			}
		});
		chatPane.add(gui.chatLine, BorderLayout.SOUTH);
		chatPane.add(chatTextPane, BorderLayout.CENTER);
		chatPane.setPreferredSize(new Dimension(200, 200));

		// Set up the main pane
		JPanel mainPane = new JPanel(new BorderLayout());
		mainPane.add(gui.statusBar, BorderLayout.SOUTH);
		mainPane.add(optionsPane, BorderLayout.WEST);
		mainPane.add(chatPane, BorderLayout.CENTER);

		// Set up the main frame
		gui.mainFrame = new JFrame("Simple TCP Chat");
		gui.mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		gui.mainFrame.setContentPane(mainPane);
		gui.mainFrame.setSize(gui.mainFrame.getPreferredSize());
		gui.mainFrame.setLocation(200, 200);
		gui.mainFrame.pack();
		gui.mainFrame.setVisible(true);
	}

	/////////////////////////////////////////////////////////////////

	// The thread-safe way to change the GUI components while
	// changing state
	private static void changeStatusTS(int newConnectStatus, boolean noError) {
		// Change state if valid state
		if (newConnectStatus != NULL) {
			connectionStatus = newConnectStatus;
		}

		// If there is no error, display the appropriate status message
		if (noError) {
			statusString = statusMensagem[connectionStatus];
		}
		// Otherwise, display error message
		else {
			statusString = statusMensagem[NULL];
		}

		// Call the run() routine (Runnable interface) on the
		// error-handling and GUI-update thread
		SwingUtilities.invokeLater(tcpObj);
	}

	/////////////////////////////////////////////////////////////////

	// The non-thread-safe way to change the GUI components while
	// changing state
	private static void changeStatusNTS(int newConnectStatus, boolean noError) {
		// Change state if valid state
		if (newConnectStatus != NULL) {
			connectionStatus = newConnectStatus;
		}

		// If there is no error, display the appropriate status message
		if (noError) {
			statusString = statusMensagem[connectionStatus];
		}
		// Otherwise, display error message
		else {
			statusString = statusMensagem[NULL];
		}

		// Call the run() routine (Runnable interface) on the
		// current thread
		tcpObj.run();
	}

	/////////////////////////////////////////////////////////////////

	// Thread-safe way to append to the chat box
	private static void appendToChatBox(String s) {
		synchronized (toAppend) {
			toAppend.append(s);
		}
	}

	/////////////////////////////////////////////////////////////////

	// Add text to send-buffer
	private static void sendString(String s) {
		synchronized (toSend) {
			toSend.append(s + "\n");
		}
	}

	/////////////////////////////////////////////////////////////////

	// limpnado for disconnect
	public static void limpando() {
		try {
			if (hostServer != null) {
				hostServer.close();
				hostServer = null;
			}
		} catch (IOException e) {
			hostServer = null;
		}

		try {
			if (socket != null) {
				socket.close();
				socket = null;
			}
		} catch (IOException e) {
			socket = null;
		}

		try {
			if (entrada != null) {
				entrada.close();
				entrada = null;
			}
		} catch (IOException e) {
			entrada = null;
		}

		if (saida != null) {
			saida.close();
			saida = null;
		}
	}

	/////////////////////////////////////////////////////////////////

	// Checks the current state and sets the enables/disables
	// accordentradagly
	public void run() {
		switch (connectionStatus) {
		case DESCONECTADO:
			gui.connectButton.setEnabled(true);
			gui.disconnectButton.setEnabled(false);
			gui.ipField.setEnabled(true);
			gui.portField.setEnabled(true);
			gui.hostOption.setEnabled(true);
			gui.guestOption.setEnabled(true);
			gui.chatLine.setText("");
			gui.chatLine.setEnabled(false);
			gui.statusColor.setBackground(Color.red);
			break;

		case DESCONECTANDO:
			gui.connectButton.setEnabled(false);
			gui.disconnectButton.setEnabled(false);
			gui.ipField.setEnabled(false);
			gui.portField.setEnabled(false);
			gui.hostOption.setEnabled(false);
			gui.guestOption.setEnabled(false);
			gui.chatLine.setEnabled(false);
			gui.statusColor.setBackground(Color.orange);
			break;

		case CONECTADO:
			gui.connectButton.setEnabled(false);
			gui.disconnectButton.setEnabled(true);
			gui.ipField.setEnabled(false);
			gui.portField.setEnabled(false);
			gui.hostOption.setEnabled(false);
			gui.guestOption.setEnabled(false);
			gui.chatLine.setEnabled(true);
			gui.statusColor.setBackground(Color.green);
			break;

		case INICIANDO_CONEXAO:
			gui.connectButton.setEnabled(false);
			gui.disconnectButton.setEnabled(false);
			gui.ipField.setEnabled(false);
			gui.portField.setEnabled(false);
			gui.hostOption.setEnabled(false);
			gui.guestOption.setEnabled(false);
			gui.chatLine.setEnabled(false);
			gui.chatLine.grabFocus();
			gui.statusColor.setBackground(Color.orange);
			break;
		}

		// Make sure that the button/text field states are consistent
		// with the internal states
		gui.ipField.setText(hostIP);
		gui.portField.setText((new Integer(port)).toString());
		gui.hostOption.setSelected(isHost);
		gui.guestOption.setSelected(!isHost);
		gui.statusField.setText(statusString);
		gui.chatText.append(toAppend.toString());
		toAppend.setLength(0);

		gui.mainFrame.repaint();
	}

	/////////////////////////////////////////////////////////////////

	// The main procedure
	public static void main(String args[]) {
		String s;

		TCPPPChat chat = new TCPPPChat();
		chat.initGUI();

		while (true) {
			try { // Poll every ~10 ms
				Thread.sleep(10);
			} catch (InterruptedException e) {
			}

			switch (connectionStatus) {
			case INICIANDO_CONEXAO:
				try {
					// Try to set up a server if host
					if (isHost) {
						hostServer = new ServerSocket(port);
						socket = hostServer.accept();
					}

					// If guest, try to connect to the server
					else {
						socket = new Socket(hostIP, port);
					}

					entrada = new BufferedReader(new InputStreamReader(socket
							.getInputStream()));
					saida = new PrintWriter(socket.getOutputStream(), true);
					changeStatusTS(CONECTADO, true);
				}
				// If error, clean up and saidaput an error message
				catch (IOException e) {
					limpando();
					changeStatusTS(DESCONECTADO, false);
				}
				break;

			case CONECTADO:
				try {
					// Send data
					if (toSend.length() != 0) {
						saida.print(toSend);
						saida.flush();
						toSend.setLength(0);
						changeStatusTS(NULL, true);
					}

					// Receive data
					if (entrada.ready()) {
						s = entrada.readLine();
						if ((s != null) && (s.length() != 0)) {
							// Check if it is the end of a trasmission
							if (s.equals(FIM_SESSAO)) {
								changeStatusTS(DESCONECTANDO, true);
							}

							// Otherwise, receive what text
							else {
								appendToChatBox("Entrada: " + s + "\n");
								changeStatusTS(NULL, true);
							}
						}
					}
				} catch (IOException e) {
					limpando();
					changeStatusTS(DESCONECTADO, false);
				}
				break;

			case DESCONECTANDO:
				// Tell other chatter to disconnect as well
				saida.print(FIM_SESSAO);
				saida.flush();

				// Clean up (close all streams/sockets)
				limpando();
				changeStatusTS(DESCONECTADO, true);
				break;

			default:
				break; // do nothing
			}
		}
	}
}

////////////////////////////////////////////////////////////////////

// Action adapter for easy event-listener coding
class ActionAdapter implements ActionListener {
	public void actionPerformed(ActionEvent e) {
	}
}

////////////////////////////////////////////////////////////////////